/** @file    LogHandlers.cpp
 *  @author  Alessandro Polo
 *  @version $Id: LogHandlers.cpp 2742 2010-07-07 19:25:00Z alex $
 *  @brief
 * File containing methods for the  class.
 * The header for this class can be found in LogHandlers.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <core/LogHandlers.h>
 
 #include <core/ObjectAllocator.h>
 #include <core/DataModels.h>

 #include <time.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// for OS INFO:
#ifdef _OS_POSIX
# include <core/posix/desktop.h>
#elif _OS_WIN32
# include <core/win32/desktop.h>
#elif _OS_WINCE
# include <core/wince/desktop.h>
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef _OS_WIN32
 #include <windows.h> // coloured output
#endif


using namespace std;
namespace wosh {

 WOSH_REGISTER_INTERFACE(wosh::LogHandler, "", 1.01, _static_LogHandler)

 WOSH_REGISTER(wosh::LogHandlerPlainText, "wosh::LogHandler", 1.01, _static_LogHandlerPlainText)
 WOSH_REGISTER(wosh::LogHandlerStdOut, "wosh::LogHandler", 1.01, _static_LogHandlerStdOut)
 WOSH_REGISTER_INTERFACE(wosh::LogHandlerMultiplexer, "wosh::LogHandler", 1.01, _static_LogHandlerMultiplexer)
 WOSH_REGISTER_INTERFACE(wosh::DefaultSharedLogHandler, "wosh::LogHandlerMultiplexer", 1.01, _static_DefaultSharedLogHandler)

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// LogHandler

// Meyer singleton! static reference is allocated on first user
// not thread safe
extern "C" {
	ListT<LogHandler*>& LogHandler::getInstances() {
		static ListT<LogHandler*> instances;
		return instances;
	}
}

LogHandler::LogHandler()
	: skip_unregister_instance(false) {
	this->id = _GET_Abs(Utilities::randomLong());
	LogHandler::getInstances().transactionBeginWrite();
	LogHandler::getInstances().push_back(this);
	LogHandler::getInstances().transactionEnd();
 }

LogHandler::~LogHandler() {
	if ( skip_unregister_instance ) return;
	LogHandler::getInstances().transactionBeginWrite();
	LogHandler::getInstances().pop(this);
	LogHandler::getInstances().transactionEnd();
 }

// uses intermediate buffer
void LogHandler::bufferTime( const char* format ) {
#ifdef _OS_WINCE
	///@bug WINCE BUG !!!!!!!!!!!!!!!!!!!
	memset(this->bufferDateTime, 0, 32 );
#elif defined(WIN32_VS)
	struct tm timeinfo;
	time_t rawtime = time(NULL);
	localtime_s(&timeinfo, &rawtime );
	strftime(this->bufferDateTime,31,format,&timeinfo);
#else
	time_t rawtime;
	time( &rawtime );
	struct tm * timeinfo;
	timeinfo = localtime( &rawtime );
	strftime(this->bufferDateTime,31,format,timeinfo);
#endif
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

DataFieldCollector<LogHandler>* LogHandler::getDataFields() {
	DataFieldCollector<LogHandler>* fields = new DataFieldCollector<LogHandler>();
	fields->add<long, Variant>(&LogHandler::getID, "ID", 0);
	fields->add<const char*,Variant>(&LogHandler::getClassName, "Type", 1);
	fields->add<bool,Variant>(&LogHandler::isOpen, "isOpen", 2);
	fields->add<std::string,Variant>(&LogHandler::getProtocol, "Protocol", 3);
	return fields;
 }

////////////////////////////////////////////////////////////////////////////////////////////////// LogHandler
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////// LogHandlerPlainText

const char* LogHandlerPlainText_intro = _Log_ENDLINE _Log_ENDLINE " ------------------------------------- Log OPENED" _Log_ENDLINE;
const char* LogHandlerPlainText_outro = _Log_ENDLINE " ------------------------------------- Log CLOSED" _Log_ENDLINE;

const char* LogHandlerPlainText__LogLevelStr[] = {
	"?U",	// (LogLevel)0 UNKNOWN
	"?N",	// (LogLevel)1 NONE
	"C!",	// (LogLevel)2 CRITICAL
	"W^",	// (LogLevel)3 WARNING
	"I-",	// (LogLevel)4 INFO
	"V_",	// (LogLevel)5 VERBOSE
	"D*",	// (LogLevel)6 DEBUG
	"?A"	// (LogLevel)7 ALL
 };

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT LogHandlerPlainText::setFileName( const std::string& filename, bool append ) {
	MutexLockerWrite mL(&this->configMux);
	if ( this->outFile != NULL ) {
		WRESULT ret_C = close_();
		if ( WFAILED(ret_C) ) return WRET_ERR_INTERNAL;
		this->outFilename = filename;
		this->appendMode = append;
		WRESULT ret_O = open_();
		if ( WFAILED(ret_O) ) return WRET_ERR_ILLEGAL_USE;
		return WRET_OK;
	 }
	this->outFilename = filename;
	this->appendMode = append;
	return WRET_OK;
 }

WRESULT LogHandlerPlainText::open() {
	MutexLockerWrite mL(&this->configMux);
	return open_();
 }

WRESULT LogHandlerPlainText::close() {
	MutexLockerWrite mL(&this->configMux);
	return close_();
 }

WRESULT LogHandlerPlainText::flush() {
	MutexLockerWrite mL(&this->configMux);
	fflush(this->outFile);
	return WRET_OK;
 }

WRESULT LogHandlerPlainText::open_() {
	if ( this->outFile != NULL || this->outFilename.empty() ) return WRET_ERR_ILLEGAL_USE;
	if ( this->appendMode )	this->outFile = fopen(this->outFilename.c_str(), "a");
	else					this->outFile = fopen(this->outFilename.c_str(), "w");
	if ( this->outFile == NULL ) return WRET_ERR_INTERNAL;
	//setbuf(this->outFile, NULL); // disable buffering
	setvbuf(this->outFile, NULL , _IOLBF , 255 ); // line buffering or N chars
	// create and decorate header
	memset(this->bufferDateTime, 0, 32 );
	bufferTime( " -- AT %Y-%m-%d %H:%M:%S" );
	fputs(LogHandlerPlainText_intro, this->outFile);
	fputs(this->bufferDateTime, this->outFile);
	std::string os_name, os_type;
	getComputerName(os_name); getOSName(os_type);
	fprintf ( this->outFile, " ON %s [%s]", os_name.c_str(), os_type.c_str() );
	fputs(_Log_ENDLINE _Log_ENDLINE, this->outFile);
	return WRET_OK;
 }

WRESULT LogHandlerPlainText::close_() {
	if ( this->outFile == NULL ) return WRET_ERR_ILLEGAL_USE;
	// create and decorate footer
	fflush(this->outFile);
	memset(this->bufferDateTime, 0, 32 );
	bufferTime( " -- AT %Y-%m-%d %H:%M:%S" );
	fputs(_Log_ENDLINE, this->outFile);
	fputs(LogHandlerPlainText_outro, this->outFile);
	fputs(this->bufferDateTime, this->outFile);
	std::string os_name, os_type;
	getComputerName(os_name); getOSName(os_type);
	fprintf ( this->outFile, " ON %s [%s]", os_name.c_str(), os_type.c_str() );
	fflush(this->outFile);
	fclose(this->outFile); this->outFile = NULL;
	return WRET_OK;
 }

WRESULT LogHandlerPlainText::setProtocol( const std::string& protocol_string ) {
	if ( protocol_string.find("file://") != 0 || protocol_string.size() <= 8 ) return WRET_ERR_PARAM;
	return setFileName( protocol_string.substr(7) );
 }

bool LogHandlerPlainText::isOpen() const {
	MutexLockerRead mL(&this->configMux);
	if ( this->outFile == NULL ) return false;
	return true;
 }

std::string LogHandlerPlainText::getProtocol() const {
	MutexLockerRead mL(&this->configMux);
	return  "file://" + this->outFilename;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void LogHandlerPlainText::log( const char* context, LogLevel level, const char* format, va_list args ) {
	this->configMux.lockForRead();
	this->writeMux.lock();
	fputs(LogHandlerPlainText__LogLevelStr[level], this->outFile);
	bufferTime( "%d@%H:%M:%S" );
	fputs(this->bufferDateTime, this->outFile);
	if ( context != NULL )
		fputs(context, this->outFile);
	vfprintf(this->outFile, format, args );
	fputs(_Log_ENDLINE, this->outFile);
	this->writeMux.unLock();
	this->configMux.unLock();
 }

///////////////////////////////////////////////////////////////////////////////////////// LogHandlerPlainText
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// LogHandlerStdOut
#ifdef _OS_POSIX
 #define _COLOR_ESC_RESET			"\033[0m"
 #define _COLOR_ESC_BLACK			"\033[0;30m"
 #define _COLOR_ESC_RED				"\033[0;31m"
 #define _COLOR_ESC_GREEN			"\033[0;32m"
 #define _COLOR_ESC_BROWN			"\033[0;33m"
 #define _COLOR_ESC_BLUE			"\033[0;34m"
 #define _COLOR_ESC_PURPLE			"\033[0;35m"
 #define _COLOR_ESC_CYAN			"\033[0;36m"
 #define _COLOR_ESC_GRAY_LIGHT		"\033[0;37m"
 #define _COLOR_ESC_GRAY_DARK		"\033[1;30m"
 #define _COLOR_ESC_RED_LIGHT		"\033[1;31m"
 #define _COLOR_ESC_GREEN_LIGHT		"\033[1;32m"
 #define _COLOR_ESC_YELLOW			"\033[1;33m"
 #define _COLOR_ESC_BLUE_LIGHT		"\033[1;34m"
 #define _COLOR_ESC_PURPLE_LIGHT	"\033[1;35m"
 #define _COLOR_ESC_CYAN_LIGHT		"\033[1;36m"
 #define _COLOR_ESC_WHITE			"\033[1;37m"
 const char* LogHandlerStdOut__LogLevelColor[] = {
	_COLOR_ESC_RESET,		// (LogLevel)0 UNKNOWN
	_COLOR_ESC_RESET,		// (LogLevel)1 NONE (not used as real level, it's a mask)
	_COLOR_ESC_RED,			// (LogLevel)2 CRITICAL
	_COLOR_ESC_BROWN,		// (LogLevel)3 WARNING
	_COLOR_ESC_BLUE,		// (LogLevel)4 INFO
	_COLOR_ESC_GRAY_DARK,	// (LogLevel)5 VERBOSE
	_COLOR_ESC_GRAY_LIGHT,	// (LogLevel)6 DEBUG
	_COLOR_ESC_RESET		// (LogLevel)7 ALL (not used as real level, it's a mask)
  };
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
#elif defined(_OS_WIN32)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
 enum Color { DARKBLUE = 1, DARKGREEN, DARKTEAL, DARKRED, DARKPINK, DARKYELLOW, GRAY, DARKGRAY, BLUE, GREEN, TEAL, RED, PINK, YELLOW, WHITE };
 #define _COLOR_ESC_RESET		WHITE
 const WORD LogHandlerStdOut__LogLevelColor[] = {
	WHITE,		// (LogLevel)0 UNKNOWN
	WHITE,		// (LogLevel)1 NONE (not used as real level, it's a mask)
	DARKRED,	// (LogLevel)2 CRITICAL
	DARKYELLOW,	// (LogLevel)3 WARNING
	BLUE,		// (LogLevel)4 INFO
	GRAY,		// (LogLevel)5 VERBOSE
	DARKGRAY,	// (LogLevel)6 DEBUG
	WHITE		// (LogLevel)7 ALL (not used as real level, it's a mask)
  };
 // Meyer singleton! static reference is allocated on first use, not thread safe (within first call)
 extern "C" {
	HANDLE getStdHandle() {
		static HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
		return hConsole;
	}
 };
#endif
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Meyer singleton! static reference is allocated on first use, not thread safe (within first call)
extern "C" {
	LogHandlerStdOut* LogHandlerStdOut::getInstance() {
		static LogHandlerStdOut* ans = new LogHandlerStdOut();
		return ans;
	}
}

WRESULT LogHandlerStdOut::open() {
	MutexLockerWrite mL(&this->configMux);
	return open_();
 }
WRESULT LogHandlerStdOut::open_() {
#ifdef _OS_WIN32
	getStdHandle();
#endif
	return WRET_OK;
 }

WRESULT LogHandlerStdOut::setProtocol( const std::string& protocol_string ) {
	MutexLockerWrite mL(&this->configMux);
	if ( protocol_string == "std://stderr" )		this->outStd = stderr;
	else if ( protocol_string == "std://stdout" )	this->outStd = stdout;
	else											return WRET_ERR_PARAM;
	open_();
	return WRET_OK;
 }

std::string LogHandlerStdOut::getProtocol() const {
	MutexLockerRead mL(&this->configMux);
	if ( this->outStd == stderr ) return "std://stderr";
	if ( this->outStd == stdout ) return "std://stdout";
	return "std://";
 }

void LogHandlerStdOut::setColoured( bool enabled ) {
	MutexLockerWrite mL(&this->configMux);
	this->printColoured = enabled;
 }

void LogHandlerStdOut::log( const char* context, LogLevel level, const char* format, va_list args ) {
	this->configMux.lockForRead();
	this->writeMux.lock();
#ifdef _OS_POSIX
	if ( this->printColoured ) // set the current color sequence
		fputs(LogHandlerStdOut__LogLevelColor[level], this->outStd);
#elif defined(_OS_WIN32)
	if ( this->printColoured ) // set the current color sequence
		SetConsoleTextAttribute(getStdHandle(), LogHandlerStdOut__LogLevelColor[level]);
#endif
	fputs(LogHandlerPlainText__LogLevelStr[level], this->outStd);
	bufferTime( "%H:%M:%S" );
	fputs(this->bufferDateTime, this->outStd);
	fputs(context, this->outStd);
	vfprintf(this->outStd, format, args );
	fputs(_Log_ENDLINE, this->outStd);
#ifdef _OS_POSIX
	if ( this->printColoured ) // unset the current color sequence
		fputs(_COLOR_ESC_RESET, this->outStd);
#elif defined(_OS_WIN32)
	if ( this->printColoured ) // set the current color sequence
		SetConsoleTextAttribute(getStdHandle(), _COLOR_ESC_RESET);
#endif
	fflush(this->outStd);
	this->writeMux.unLock();
	this->configMux.unLock();
 }

//////////////////////////////////////////////////////////////////////////////////////////// LogHandlerStdOut
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Meyer singleton: Instantiated on first use. Guaranteed to be destroyed.
extern "C" {
	DefaultSharedLogHandler* DefaultSharedLogHandler::getInstance() {
		static DefaultSharedLogHandler instance_DefaultSharedLogHandler;
		//if ( !instance_DefaultSharedLogHandler.isOpen() )
		//	return NULL;
		return &instance_DefaultSharedLogHandler;
	}
}

DefaultSharedLogHandler::~DefaultSharedLogHandler() {
	this->configMux.lockForRead();
	this->writeMux.lock();
	for( int i=0; i<4; ++i ) {
		if ( this->handlers[i] == NULL ) continue;
		delete this->handlers[i]; this->handlers[i] = NULL;
	 }
	this->writeMux.unLock();
	this->configMux.unLock();
 }

WRESULT DefaultSharedLogHandler::setStdOut( bool enabled, bool coloured ) {
	this->configMux.lockForWrite();
	if ( !enabled ) {
		delete this->handlers[0]; this->handlers[0] = NULL;
		this->configMux.unLock();
		return WRET_OK;
	 }
	else if ( this->handlers[0] == NULL ) {
		this->handlers[0] = new LogHandlerStdOut();
	 }
	static_cast<LogHandlerStdOut*>(this->handlers[0])->setColoured( coloured );
	WRESULT ret = this->handlers[0]->open();
	this->configMux.unLock();
	return ret;
 }

WRESULT DefaultSharedLogHandler::setFileName( const std::string& filename, bool append ) {
	this->configMux.lockForWrite();
	if ( this->handlers[1] == NULL ) {
		this->handlers[1] = new LogHandlerPlainText();
	 }
	WRESULT ret = static_cast<LogHandlerPlainText*>(this->handlers[1])->setFileName( filename, append );
	if ( WSUCCEEDED(ret) && !this->handlers[1]->isOpen() )
		ret = this->handlers[1]->open();
	if ( WFAILED(ret) ) { // better to handle that, probably filename was invalid!
		fprintf(stderr, "Can't open LogHandlerPlainText (Invalid Filename: %s?), forcing default 'wosh.log'!\n", filename.c_str() );
		ret = static_cast<LogHandlerPlainText*>(this->handlers[1])->setFileName( "wosh.log", append );
		ret = this->handlers[1]->open();
	 }
	this->configMux.unLock();
	return ret;
 }

std::string DefaultSharedLogHandler::getFileName() const {
	this->configMux.lockForWrite();
	LogHandlerPlainText* phanlder = static_cast<LogHandlerPlainText*>(this->handlers[1]);
	if ( phanlder == NULL ) {
		this->configMux.unLock();
		return "";
	 }
	string filename = phanlder->getFileName();
	this->configMux.unLock();
	return filename;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

}; // namespace wosh
